/*
 * $Id$
 */
package py.windmill.util;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Xml {

    private static Xml EMPTY_XML = new Xml() {

        @Override
        public Xml child(final String name) {
            return this;
        }

        @Override
        public List<Xml> children(String name) {
            return Collections.EMPTY_LIST;
        }
         
    };

    private String name;
    private String content;
    private Map<String, String> nameAttributes = new HashMap<String, String>();
    private Map<String, List<Xml>> nameChildren = new HashMap<String, List<Xml>>();

    /**
     *
     * @param is
     * @param rootName
     * @return
     */
    private static Element rootElement(final InputStream is, String rootName) {
        try {
            final DocumentBuilderFactory 
              builderFactory = DocumentBuilderFactory.newInstance();
            final DocumentBuilder builder = builderFactory.newDocumentBuilder();
            final Document document = builder.parse(is);
            final Element rootElement = document.getDocumentElement();
            if (!rootElement.getNodeName().equals(rootName)) {
                throw new RuntimeException("Could not find root node: " + rootName);
            }
            return rootElement;
        } 
        catch (final Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 
     * @param filename
     * @param rootName
     * @return 
     */
    public static Xml file(String filename, String rootName) {
        try {
            final FileInputStream fis = new FileInputStream(filename);
            try {
                return new Xml(fis, rootName);
            } 
            finally {
                fis.close();
            }
        } 
        catch (Exception exception) {
            throw new RuntimeException(exception);
        }
    }

    /**
     * 
     * @param is
     * @param rootName 
     */
    public Xml(final InputStream is, final String rootName) {
        this(rootElement(is, rootName));
    }

    /**
     * 
     * @param element 
     */
    private Xml(Element element) {
        this.name = element.getNodeName();
        this.content = element.getTextContent();
        NamedNodeMap namedNodeMap = element.getAttributes();
        int n = namedNodeMap.getLength();
        for (int i = 0; i < n; i++) {
            Node node = namedNodeMap.item(i);
            String name = node.getNodeName();
            addAttribute(name, node.getNodeValue());
        }
        NodeList nodes = element.getChildNodes();
        n = nodes.getLength();
        for (int i = 0; i < n; i++) {
            Node node = nodes.item(i);
            int type = node.getNodeType();
            if (type == Node.ELEMENT_NODE) {
                addChild(node.getNodeName(), new Xml((Element) node));
            }
        }
    }

    /**
     * 
     */
    private Xml() {}

    private void addAttribute(String name, String value) {
        nameAttributes.put(name, value);
    }

    private void addChild(String name, Xml child) {
        List<Xml> children = nameChildren.get(name);
        if (children == null) {
            children = new ArrayList<Xml>();
            nameChildren.put(name, children);
        }
        children.add(child);
    }

    public String name() {
        return name;
    }

    public String content() {
        return content;
    }

    public Xml child(final String name) {
        List<Xml> children = children(name);
        switch (children.size()) {
            case 0 : return EMPTY_XML;
            case 1 : return children.get(0);
            default:
                throw new RuntimeException("Could not find individual child node: " + name);
        }
    }

    public List<Xml> children(final String name) {
        List<Xml> children = nameChildren.get(name);
        return children == null ? Collections.EMPTY_LIST : children;
    }

    public String string(final String name) {
        String value = nameAttributes.get(name);
        if (value == null) {
            throw new RuntimeException("Could not find attribute: " + name + ", in node: " + this.name);
        }
        return value;
    }

    public int integer(String name) {
        return Integer.parseInt(string(name));
    }

    /*
     public String[] e4xEval(String path) {
     Boolean isAttribute = path.contains("@");
     path = "//" + path.replace(".", "/");
     XPathFactory factory = XPathFactory.newInstance();
     XPath xpath = factory.newXPath();
     XPathExpression expr = null;
     String[] res = new String[0];

     try {
     expr = xpath.compile(path + (isAttribute ? "" : "/text()"));
     } catch (XPathExpressionException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
     }
     Object result = null;
     try {
     result = expr.evaluate(doc, XPathConstants.NODESET);
     } catch (XPathExpressionException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
     }

     NodeList nodes = (NodeList) result;
     res = new String[nodes.getLength()];
     for (int i = 0; i < nodes.getLength(); i++) {
     res[i] = nodes.item(i).getNodeValue();
     }

     return res;
     }
     */
}